#include <stdio.h>
#include <stdlib.h>

#include <openssl/bn.h>

#include "functions.h"
#include "trace.h"

/*  Description in functions.h */
int crt(int* a, int* m, int k, int* x, int* rez_M) {
	int rc = 0;
	int i = 0;
	int j = 0;

	if((NULL == a) || (NULL == m) || (NULL == x)) {
		TRACE_ERROR("Invalid null parameters\n");
		return 1;
	}

	// check preconditions
	for(i=0; i<k; i++) {
		if(a[i]>m[i]){
			TRACE_ERROR("Invalid coefficients for crt\n");
			return 1;
		}
	}

	for(i=0; i<k; i++) {
		for(j=i+1; j<k; j++){
			if(gcd(m[i], m[j]) != 1) {
				TRACE_ERROR("Invalid precondition: gcd(%d,%d)!=1\n",
						m[i], m[j]);
				return 2;
			}
		}
	}

	// Demo purpose! Could easily overflow!
	int M = 1;
	for(i=0; i<k; i++) {
		M *= m[i];
	}

	int rez = 0;
	for(i=0; i<k; i++) {
		int Mi = M / m[i];

		rez += a[i] * Mi * inv_mod(Mi, m[i]);
	}
	rez = rez % M;

	//  Check solution
	for(i=0; i<k; i++){
		if((rez % m[i]) != a[i]) {
			TRACE_ERROR("Solution checking error. Possible overflow above\n");
			return 3;
		}
	}
	TRACE_INFO("Solution verifcation OK\n");

	*x = rez;
	*rez_M = M;

	return rc;
}

int crt_bn(char **a, char **m, int k, char **x, char **rez_M) {
	int rc = 0;

	if((NULL == a) || (NULL == m) || (NULL == x) || (NULL == rez_M)) {
		TRACE_ERROR("Invalid null parameters\n");
		return 1;
	}

	BIGNUM **coef_a = (BIGNUM**)malloc(k * sizeof(BIGNUM*));
	BIGNUM **mods_m = (BIGNUM**)malloc(k * sizeof(BIGNUM*));

	for(int i=0; i<k; i++){
		TRACE_DEBUG("a[%d]=%s m[%d]=%s\n",i, a[i], i, m[i]);
		// convert strings to BN
		coef_a[i] = NULL;
		BN_dec2bn(&coef_a[i], a[i]);

		mods_m[i] = NULL;
		BN_dec2bn(&mods_m[i], m[i]);
	}

	// check preconditions
	for(int i=0; i<k; i++) {
		if(BN_cmp(coef_a[i], mods_m[i]) == 1){
			TRACE_ERROR("Invalid coefficients for crt\n");
			return 1;
		}
	}

	BN_CTX *ctx=NULL;
	ctx = BN_CTX_new();
	if (ctx == NULL) {
		TRACE_ERROR("Invalid ctx for gcd\n");
		return 1;
	}

	for(int i=0; i<k; i++) {
		for(int j=i+1; j<k; j++){
			BIGNUM* rez = NULL;
			rez = BN_CTX_get(ctx);
			if (!BN_gcd(rez, mods_m[i], mods_m[j], ctx)) {
				TRACE_ERROR("Invalid gcd calc for gcd\n");
				return 1;
			}

			if (!BN_is_one(rez)){
				TRACE_ERROR("Invalid precondition: gcd(%s,%s)!=1\n",
						m[i], m[j]);
				return 2;
			}
		}
	}

	BIGNUM *M = NULL;
	M = BN_CTX_get(ctx);

	BN_dec2bn(&M, "1");

	for(int i=0; i<k; i++) {
		if (!BN_mul(M, M, mods_m[i], ctx)) {
			TRACE_ERROR("Multiply error in computing M\n");
			return 1;
		}
	}

	BIGNUM *rez = NULL;
	rez = BN_CTX_get(ctx);

	BN_dec2bn(&rez, "0");

	for(int i=0; i<k; i++) {
		BIGNUM *Mi = NULL;
		Mi = BN_CTX_get(ctx);

		// Mi = M / m[i]
		BN_div(Mi, NULL, M, mods_m[i], ctx);

		BIGNUM *Ni = NULL;
		Ni = BN_CTX_get(ctx);

		if ((BN_mod_inverse(Ni, Mi, mods_m[i], ctx)) == NULL){
			// Ni=inv(Mi, m[i]
			TRACE_ERROR("BN inverse error\n");
			return 1;
		}

		// Mi * Ni
		BN_mul(Mi, Mi, Ni, ctx);

		// Mi * Ni * a[i]
		BN_mul(Mi, Mi, coef_a[i], ctx);

		//rez += a[i] * Mi * Ni;
		BN_add(rez, rez, Mi);
	}

	BN_mod(rez, rez, M, ctx);

	//  Check solution
	for(int i=0; i<k; i++){
		BIGNUM *tmp = NULL;
		tmp = BN_CTX_get(ctx);

		BN_mod(tmp, rez, mods_m[i], ctx);
		if(BN_cmp(coef_a[i], tmp) != 0){
			TRACE_ERROR("Solution checking error\n");
			return 3;
		}
	}

	TRACE_INFO("Solution verifcation OK\n");

	*x = BN_bn2dec(rez);
	*rez_M = BN_bn2dec(M);

	return rc;
}
